//===============================================================================
// Microsoft patterns & practices
// Web Client Software Factory
//-------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//-------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using WCSFContrib.PageFlow.Services;
using WCSFContrib.PageFlow.WorkflowFoundation.Activities;
using WCSFContrib.PageFlow.WorkflowFoundation.Properties;
using System.Globalization;
using WCSFContrib.PageFlow.Utils;

namespace WCSFContrib.PageFlow.WorkflowFoundation
{
	/// <summary>
	/// Implementation of the <see cref="IPageFlow">IPageFlow</see>	interface 
	/// utilizing Windows Workflow Foundation.
	/// </summary>
    public class WorkflowFoundationPageFlow : IPageFlow
    {
        private const string PageFlowStatusInternalDataKey = "__$PageFlowStatus";

        private IPageFlowDefinition _definition;
        private WorkflowInstance _instance;
        private INavigationService _navigationService;
        private PageFlowStatus _status;
        private WorkflowFoundationPageFlowState _userData;
        private WorkflowFoundationPageFlowState _internalData;        
        private StateMachineQuery _stateMachineQuery;
        private bool _terminated;

		/// <summary>
		/// Creates an instance of a WorkflowFoundationPageFlow using the
		/// provided <see cref="WorkflowInstance">WorkflowInstance</see>,
		/// <see cref="INavigationService">INavigationService</see>, and
		/// <see cref="WorkflowFoundationPageFlowDefinition">WorkflowFoundationPageFlowDefinition</see>.
		/// </summary>
		/// <param name="instance">Workflow instance to use.</param>
		/// <param name="service">The navigation service to use in this PageFlow.</param>
		/// <param name="definition">The definition of the PageFlow.</param>
        public WorkflowFoundationPageFlow(WorkflowInstance instance, INavigationService service, WorkflowFoundationPageFlowDefinition definition)
        {
            if (instance == null)
                throw new ArgumentNullException("instance");
            if (definition == null)
                throw new ArgumentNullException("definition");

            _navigationService = service;
            _instance = instance;
            _userData = new WorkflowFoundationPageFlowState(_instance);
            _internalData = new WorkflowFoundationPageFlowState(_instance);

            _definition = definition;
            _stateMachineQuery = new StateMachineQuery(definition.Definition, _instance);
        }

		/// <summary>
		/// Occurs when the execution of a page flow is aborted.
		/// </summary>
        public event EventHandler<PageFlowAbortedEventArgs> Aborted;

		/// <summary>
		/// Occurs when the execution of a page flow is started.
		/// </summary>
		public event EventHandler<PageFlowEventArgs> Started;

		/// <summary>
		/// Occurs when the execution of a started page flow is suspended.
		/// </summary>
		public event EventHandler<PageFlowEventArgs> Suspended;

		/// <summary>
		/// Occurs when the execution of a suspended page flow is resumed.
		/// </summary>
		public event EventHandler<PageFlowEventArgs> Resumed;

		/// <summary>
		/// Occurs when the execution of a page flow completes.
		/// </summary>
		public event EventHandler<PageFlowEventArgs> Completed;

		/// <summary>
		/// Gets the current page of the page flow.
		/// </summary>
		public Page CurrentPage
        {
            get
            {
                string stateName = _stateMachineQuery.CurrentStateName;
                return stateName == null ? null : _definition.GetPage(stateName);
            }
        }

		/// <summary>
		/// Gets the page flow definition.
		/// </summary>	
		public IPageFlowDefinition Definition
        {
            get { return _definition; }
        }

		/// <summary>
		/// Gets the page flow id.
		/// </summary>
		public Guid Id
        {
            get { return _instance.InstanceId; }
        }

		/// <summary>
		/// Gets the page flow status.
		/// </summary>
		public PageFlowStatus Status
        {
            get {
                if (_stateMachineQuery.IsWorkflowQueueDataAvailable() && !_terminated)
                {
                    return (PageFlowStatus)_internalData[PageFlowStatusInternalDataKey];
                }
                else
                {
                    return _status;
                }
            }
        }

		/// <summary>
		/// Gets the page flow transitions.
		/// </summary>
		public ReadOnlyCollection<string> Transitions
        {
            get
            {
                if (!_stateMachineQuery.IsWorkflowQueueDataAvailable())
                    throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.CannotGetTransitions, this.Id));
            
                List<string> transitions = new List<string>();

                foreach (Activity activity in _stateMachineQuery.CurrentState.Activities)
                {
                    if (activity is EventDrivenActivity)
                    {
                        transitions.Add(activity.Name);
                    }
                }

                return transitions.AsReadOnly();
            }
        }

		/// <summary>
		/// Gets the page flow user data.
		/// </summary>
		public PageFlowState UserData
        {
            get { return _userData; }
        }

		/// <summary>
		/// Aborts page flow execution.
		/// </summary>
		/// <param name="redirectToAbortPage">True if the page flow should navigate to the abort page following the abort; false otherwise.</param>
		public void Abort(bool redirectToAbortPage)
        {
            if (!_stateMachineQuery.IsWorkflowQueueDataAvailable())
                throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.CannotAbortPageFlow, this.Id));
            
            // Using Terminate instead of Abort since we not only want to remove the in memory version, 
            // but we want to kill the instance from the persistence store.
            _instance.Terminate(Resources.TerminateWorkflowReasonAborted);
            RunWorkflow();
            _status = PageFlowStatus.Terminated;
            _terminated = true;

            if (Aborted != null)
                Aborted(this, new PageFlowAbortedEventArgs(Id));

            if (redirectToAbortPage)
            {
                if (_definition.AbortPage.Url != null)
                    _navigationService.RedirectTo(_definition.AbortPage);
            }
        }

		/// <summary>
		/// Tests whether the page flow can redirect to a given page.
		/// </summary>
		/// <param name="pageName">The name of the target page.</param>
		/// <returns>True if the redirect if possible, false otherwise.</returns>
        public bool CanRedirectTo(string pageName)
        {
            if (_definition.Constrained)
            {
                if (pageName == CurrentPage.Name)
                {
                    return true;
                }

                return false;
            }
            else
            {
                return true;
            }
        }

		/// <summary>
		/// Force the page flow to a specific target page.
		/// </summary>
		/// <param name="transitionName">The target page.</param>
		/// <remarks>With this method the developer specifies the target page, overriding the page flow state machine. </remarks>
		/// <exception cref="NavigationException">thrown when the current page cannot follow the requested transition.
		/// </exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods", Justification = "Validation done by Guard class.")]
        public void Navigate(string transitionName)
        {
			Guard.ArgumentNotNullOrEmptyString(transitionName, "transitionName");

			if (!CurrentPage.Transitions.Contains(transitionName))
			{
				throw new NavigationException(string.Format(CultureInfo.CurrentCulture, Resources.TransitionNotExists, transitionName));
			}

            _instance.EnqueueItem(transitionName.ToUpper(CultureInfo.CurrentCulture), transitionName, null, null);

            RunWorkflow();
            _navigationService.RedirectTo(CurrentPage);
        }

		/// <summary>
		/// Redirects the page flow to the current page.
		/// </summary>
		public void RedirectToCurrentPage()
        {
            _navigationService.RedirectTo(CurrentPage);
        }

		/// <summary>
		/// Sets the current page.
		/// </summary>
		/// <param name="pageName">The name of the page.</param>
		public void SetCurrentPage(string pageName)
        {
            if (_definition.Constrained)
            {
                if (pageName != CurrentPage.Name)
                    throw new NavigationException(Resources.CannotSetStateConstrained);
            }
            else
            {
                _stateMachineQuery.SetState(pageName);

                RunWorkflow();
            }
        }

		/// <summary>
		/// Starts the page flow.
		/// </summary>
		public void Start()
        {
            if (!_stateMachineQuery.IsWorkflowQueueDataAvailable())
            {
                _instance.Start();
                RunWorkflow();
            }
            
            _internalData[PageFlowStatusInternalDataKey] = PageFlowStatus.Running;
            
            if (Started != null)
                Started(this, new PageFlowEventArgs(this));            
        }

		/// <summary>
		/// Suspend the execution of the page flow such that it can be resumed later.
		/// </summary>
		public void Suspend()
        {
            if (!_stateMachineQuery.IsWorkflowQueueDataAvailable())
                throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.CannotSuspendPageFlow, this.Id));

            _internalData[PageFlowStatusInternalDataKey] = PageFlowStatus.Suspended;

            if (Suspended != null)
                Suspended(this, new PageFlowEventArgs(this));

            _instance.Unload();
            RunWorkflow();
        }

		/// <summary>
		/// Resume the execution of a suspended page flow.
		/// </summary>
		public void Resume()
        {
            if (!_stateMachineQuery.IsWorkflowQueueDataAvailable())
                throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.CannotResumePageFlow, this.Id));

            _internalData[PageFlowStatusInternalDataKey] = PageFlowStatus.Running;

            if (Resumed != null)
                Resumed(this, new PageFlowEventArgs(this));
        }

		/// <summary>
		/// Advances the page flow to the next page.
		/// </summary>
		/// <remarks>The page flow state machine determines the next page.</remarks>
		public void Next()
        {
            Navigate(CurrentPage.DefaultTransition);
        }

		/// <summary>
		/// Changes the status of an executing page flow, marking it as completed.
		/// </summary>
		public void Complete()
        {
            _status = PageFlowStatus.Terminated;
            
            if (Completed != null)
                Completed(this, new PageFlowEventArgs(this));

            _instance.Terminate(Resources.TerminateWorkflowReasonComplete);
            RunWorkflow();

            _terminated = true;
        }

		/// <summary>
		/// Gets the WorkflowInstance for this PageFlow
		/// </summary>
		public WorkflowInstance WorkflowInstance
		{
			get { return _instance; }
		}

		/// <summary>
		/// Determines whether the specified Object is equal to the current WorkflowFoundationPageFlow. 
		/// </summary>
		/// <param name="obj">The object to compare the current instance to.</param>
		/// <returns><see langword="true"/> if the specified Object is equal to the current Object; 
		/// otherwise, <see langword="false"/>.</returns>
		public override bool Equals(object obj)
		{
			WorkflowFoundationPageFlow wf = obj as WorkflowFoundationPageFlow;
			if (wf == null)
			{
				return false;
			}
			else
			{
				return wf.Id.Equals(this.Id);
			}
		}

        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>A hash code for the current Object.</returns>
        public override int GetHashCode()
        {
            return this.Id.GetHashCode();
        }

		private void RunWorkflow()
		{
			ManualWorkflowSchedulerService workflowScheduler =
				_instance.WorkflowRuntime.GetService<ManualWorkflowSchedulerService>();
			workflowScheduler.RunWorkflow(_instance.InstanceId);
		}
    }
}
